/*
Copyright (C) 2009-2010 Xanadu Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2
of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "../provider/DataProvider.h"
#include "Map.h"
#include "MapPlayers.h"
#include "../client/MapData.h"
#include "MapMobs.h"
#include "MapReactors.h"
#include "MapNPCs.h"
#include "MapDrops.h"
#include "../client/MapsData.h"
#include "../client/MapData.h"
#include "../client/MapFootholdsData.h"
#include "../client/MapFootholdData.h"
#include "../client/MapPortalsData.h"
#include "../client/MapPortalData.h"
#include "../tools/PacketCreator.h"
#include "PlayerShop.h"
#include "Event.h"
#include "Mob.h"
#include "Reactor.h"
#include "PvP.h"
#include "../tools/Tools.h"
#include "../tools/Timer.h"

Map::Map(Channel* channel, int id) :
id(id),
channel(channel),
pvp(0),
spawn(spawn),
mevent(0),
type(MapsData::getInstance()->getDataByID(id)->getFieldType()),
//timers(new Timer::Container),
music(MapsData::getInstance()->getDataByID(id)->getMusic()),
ship(false),
shipData(MapsData::getInstance()->getDataByID(id)->getShip())
{
	players = new MapPlayers(this);
	mobs = new MapMobs(this);
	npcs = new MapNPCs(this);
	drops = new MapDrops(this);
	reactors = new MapReactors(this);
	vector <MapPortalData*>* pl = MapsData::getInstance()->getDataByID(id)->getPortalsData()->getData();
	for(int i=0; i<(int)pl->size(); i++) {
		portals[(*pl)[i]->getFromPortal()] = (*pl)[i];
	}
	//new Timer::Timer(std::tr1::bind(&Map::runTimer, this), Timer::Id(Timer::Types::MapTimer, id, 0), getTimers(), 0, 10 * 1000);
}

Drop* Map::getDrop(int id) {
	return drops->getDropByID(id);
}

Mob* Map::getMob(int id) {
	return mobs->getMobByID(id);
}

NPC* Map::getNPC(int id) {
	return npcs->getNPCByID(id);
}

Player* Map::getPlayer(int id) {
	return players->getPlayerByID(id);
}

Reactor* Map::getReactor(int id) {
	return reactors->getReactorByID(id);
}

void Map::runTimer() {
	if (getPlayers()->getPlayers()->empty()) {
		mobs->clear();
		return;
	}
	mobs->checkSpawn();
	reactors->checkSpawn();
	drops->checkDrops();
}

void Map::addPlayer(Player* player) {
	players->add(player);
	mobs->show(player);
	mobs->checkSpawn();
	npcs->show(player);
	npcs->checkspawn(player);
	reactors->show(player);
	reactors->checkSpawn();
	drops->show(player);
	drops->checkDrops();
	if(pvp != 0)
		pvp->showPvP(player);
}

void Map::removePlayer(Player* player) {
	players->remove(player);
	mobs->checkSpawn();
}

Position Map::getPortalPosition(int pid) {
	Position p;
	p.x = 0;
	p.y = 0;
	MapPortalData* pd = MapsData::getInstance()->getDataByID(id)->getPortalsData()->getDataByID(pid);
	if(pd != 0) {
		p.x = pd->getX();
		p.y = pd->getY();
	}
	return p;
}

MapPortalData* Map::getPortal(int pid) {
	return MapsData::getInstance()->getDataByID(id)->getPortalsData()->getDataByID(pid);
}

void Map::send(PacketWriter* pw) {
	players->send(pw);
}

void Map::send(PacketWriter* pw, Player* player) {
	players->send(pw, player);
}

void Map::send(PacketWriter* pw, Player* player, Position pos) {
	players->send(pw, player, pos);
}

int Map::getReturnMap() {
	return DataProvider::getInstance()->getReturnMap(id);
}

Mob* Map::spawnMob(int id, int x, int y) {
	Position pos;
	int fh;
	pos.x = x;
	pos.y = y;
	pos = findFoothold(pos, &fh);
	return mobs->spawnMob(id, pos.x, pos.y, -1, -1, -1, fh);
}

Reactor* Map::spawnReactor(int id, int x, int y) {
	Position pos;
	pos.x = x;
	pos.y = y;
	return reactors->spawnReactor(id, pos.x, pos.y);
}

void Map::killMob(Mob* mob) {
	if(mob->getMap() == this)
		mobs->kill(mob, 0);
}

void Map::destroyReactor(Reactor* reactor) {
	if(reactor->getMap() == this)
		reactors->destroy(reactor);
}

int Map::getMobsCount() {
	return mobs->getMobsCount();
}

Reactor* Map::getReactorByReactorID(int id) {
	return reactors->getReactorByReactorID(id);
}

void Map::setSpawn(bool spawn) {
	this->spawn = spawn;
	if(spawn) {
		mobs->checkSpawn();
	}
}

void Map::clearDrops() {
	drops->clear();
}

void Map::clearReactors() {
	reactors->clear();
}

void Map::clearMobs() {
	mobs->clear();
}

void Map::clear() {
	clearMobs();
	clearDrops();
	clearReactors();
}

int Map::getClosestSpawnPos(Position pos) {
	vector <MapPortalData*>* pl = MapsData::getInstance()->getDataByID(id)->getPortalsData()->getData();
	if(pl->size() <= 0)
		return 0;
	Position ppos;
	ppos.x = (*pl)[0]->getX();
	ppos.y = (*pl)[0]->getY();
	int close = 0; 
	int closed = Tools::getDistance(ppos, pos);
	for(int i=1; i<(int)pl->size(); i++) {
		ppos.x = (*pl)[i]->getX();
		ppos.y = (*pl)[i]->getY();
		int dis = Tools::getDistance(ppos, pos);
		if(dis < closed) {
			close = i;
			closed = dis;
		}
	}
	return close;
}

Position Map::findFoothold(Position pos, int* fh) {
	short x = pos.x;
	short y = pos.y;
	MapData* md = MapsData::getInstance()->getDataByID(id);
	if(md == 0)
		return pos;
	vector <MapFootholdData*>* tdata = md->getFootholdsData()->getData();
	bool first = 1;
	short x1, x2, y1, y2, maxy;
	for(int i=0; i<(int)tdata->size(); i++) {
		MapFootholdData* data = (*tdata)[i];
		x1 = data->getX1();
		x2 = data->getX2();
		y1 = data->getY1();
		y2 = data->getY2();
		if((x >= x1 && x <= x2) || (x >= x2 && x <= x1)) {
			if(first) {
				if(x1 == x2) {
					continue;
				}
				maxy = (short) ( (double)( y1 - y2 ) / ( x1 - x2 ) * (x - x1) + y1 );
				if(fh != 0)
					*fh = data->getID();
				if(maxy >= y)
					first = 0;
			}
			else{
				if(x1 == x2) {
					continue;
				}
				short cmax = (short) ( (double)( y1 - y2 ) / ( x1 - x2 ) * (x - x1) + y1 );
				if(cmax < maxy && cmax >= y) {
					if(fh != 0)
						*fh = data->getID();
					maxy = cmax;
				}
			}
		}
	}
	if(!first) {
		Position newpos;
		newpos.x = x;
		newpos.y = maxy;
		return newpos;
	}
	return pos;
}

void Map::startPvP() {
	if(pvp == 0) {
		pvp = new PvP(this);
		pvp->startPvP();
	}
}

void Map::stopPvP() {
	if(pvp == 0)
		return;
	pvp->endPvP();
	delete pvp;
	pvp = 0;
}

void Map::setEvent(Event* e) {
	this->mevent = e;
	if(e != 0) {
		send(PacketCreator().showTimer(e->getTime()));
	}
}

void Map::setShip(bool s) {
	if(shipData != NULL) {
		ship = s;
		send(PacketCreator().showShip((shipData->getType() == 0) ? (s ? 0 : 2) : (s ? 4 : 5), shipData->getType()));
	}
}
void Map::showMessage(string& msg) {
	send(PacketCreator().showMessage(msg, 5));
}

void Map::changeMusic(string& name) {
	send(PacketCreator().changeMusic(name));
}

void Map::playSound(string& name) {
	send(PacketCreator().playSound(name));
}

void Map::showEffect(string& name) {
	send(PacketCreator().showEffect(name));
}

void Map::showObject(string& name) {
	send(PacketCreator().sendEffect(name));
}

// PlayerShops
void Map::addPlayerShop(PlayerShop *shop) {
	playershops[shop->getId()] = shop;
}

void Map::removePlayerShop(int id) {
	if (playershops.find(id) != playershops.end()) {
		playershops.erase(id);
	}
}

PlayerShop* Map::getPlayerShop(int id) {
	return (playershops.find(id) != playershops.end() ? playershops[id] : 0);
}